const express = require('express');
const { Course, Membership, User, Order } = require('../models');
const { NotFound, BadRequest } = require('http-errors');
const { failure, success } = require('../utils/responses');
const { getKey, setKey } = require('../utils/redis');
const router = express.Router();
const userAuth = require('../middlewares/user-auth');
const logger = require('../utils/logger');

const alipaySdk = require('../utils/alipay');
const order = require('../models/order');
const moment = require('moment');
const { sequelize } = require('../models');

/* 
支付宝支付
POST /alipay/pay/page 电脑页面
POST /alipay/pay/wap 手机页面
 */
router.post('/pay/:platform', userAuth, async function (req, res) {
  try {
    const isPC = req.params.platform === 'page';
    const method = isPC ? 'alipay.trade.page.pay' : 'alipay.trade.wap.pay';
    const productCode = isPC ? 'FAST_INSTANT_TRADE_PAY' : 'QUICK_WAP_WAY';
    // 支付订单信息
    const order = await getOrder(req);
    const { outTradeNo, totalAmount, subject } = order;
    const bizContent = {
      product_code: productCode,
      out_trade_no: outTradeNo,
      subject: subject,
      total_amount: totalAmount,
    };

    // 支付页面接口，返回HTML代码片段
    const url = alipaySdk.pageExecute(method, 'GET', {
      bizContent,
      returnUrl: process.env.ALIPAY_RETURN_URL,
      notify_url: process.env.ALIPAY_NOTIFY_URL, // 必须是部署在线上的地址
    });
    success(res, '支付地址生成成功', {
      url,
    });
  } catch (error) {
    failure(res, error);
  }
});

/**
 * 支付宝异步通知
 * post /alipay/notify
 * */

router.post('/notify', async function (req, res) {
  try {
    const alipayData = req.body;
    logger.info('支付宝异步通知信息：', alipayData);
    const verify = alipaySdk.checkNotifySign(alipayData);
    if (verify) {
      const { out_trade_no, trade_no, gmt_payment } = alipayData;
      await paidSuccess(out_trade_no, trade_no, gmt_payment);
      res.send('success');
    } else {
      logger.warn('支付宝验签失败：', alipayData);
      res.send('fail');
    }
    res.send();
  } catch (error) {
    failure(res, error);
  }
});

/**
 * 支付宝支付成功后，跳转页面
 * GET /alipay/finish
 * */

router.get('/finish', async function (req, res) {
  try {
    const alipayData = req.query;
    const verify = alipaySdk.checkNotifySign(alipayData);
    if (verify) {
      const { out_trade_no, trade_no, timestamp } = alipayData;
      await paidSuccess(out_trade_no, trade_no, timestamp);
      // res.send('支付成功')
      res.redirect('https://clwy.cn/users/course_orders');
    } else {
      throw new BadRequest('支付验签失败。');
    }
  } catch (error) {
    failure(res, error);
  }
});

/**
 * 主动查询支付宝订单状态
 * POST /alipay/query
 * */

router.post('/query', userAuth, async function (req, res) {
  try {
    // 查询订单
    const order = await getOrder(req);
    const result = await alipaySdk.exec('alipay.trade.query', {
      bizContent: {
        out_trade_no: order.outTradeNo,
      },
    });
    const { tradeStatus, outTradeNo, tradeNo, sendPayDate } = result;
    if (tradeStatus === 'TRADE_SUCCESS') {
      await paidSuccess(outTradeNo, tradeNo, sendPayDate);
    }

    success(res, '查询成功', { result });
  } catch (error) {
    failure(res, error);
  }
});

/**
 * 支付成功后，更新订单状态和会员信息
 * @param alipayData
 * @returns {Promise<void>}
 * */

async function paidSuccess(outTradeNo, tradeNo, paidAt) {
  try {
    await sequelize.transaction(async (t) => {
      // 查询当前订单 , lock: true 增加排他锁 保证事务一致性与完整性
      const order = await Order.findOne({
        where: { outTradeNo: outTradeNo },
        transaction: t,
        lock: true,
      });
      // 对于订单状态已更新的订单，直接返回，防止用户重复请求，重复增加大会员有效期
      if (order.status > 0) {
        return;
      }
      // 更新订单状态
      await order.update(
        {
          tradeNo: tradeNo, // 流水号
          status: 1, // 订单状态已支付
          paymentMethod: 0, // 支付方式： 支付宝
          paidAt: paidAt, // 支付时间
        },
        { transaction: t }
      );
      // 查询订单对应的用户
      const user = await User.findByPk(order.userId, { transaction: t, lock: true });
      // 将用户组设置为大会员。可防止管理员创建订单并将用户组修改为大会员。
      if (user.role === 0) {
        user.role = 1;
      }
      // 使用moment.js增加大会员有效期
      user.membershipExpiredAt = moment(user.membershipExpiredAt || new Date())
        .add(order.membershipMonths, 'months')
        .toDate();
      // 保存用户信息
      await user.save({ transaction: t });
    });
  } catch (error) {
    throw error;
  }
}

/**
 * 公共方法查询当前订单
 * */
const getOrder = async (req) => {
  const { outTradeNo } = req.body;
  if (!outTradeNo) {
    throw new BadRequest('订单号不能为空');
  }
  const order = await Order.findOne({
    where: {
      outTradeNo,
      userId: req.userId,
    },
  });

  if (!order) {
    throw new NotFound(`订单: ${outTradeNo} 的订单未找到`);
  }

  if (order.status > 0) {
    throw new BadRequest('订单已支付或取消');
  }

  return order;
};

module.exports = router;

// 3.1.乐观锁与悲观锁的适用场景
// 乐观锁:适用于读多写少 的场景，尤其是在高并发环境下，冲突发生的概率较低时，它假设数据在大多数情况下不会被修改，因此在提交更新之前不需要加锁，而是在提交时，通过 版本号 或 时间戳 检查是否有冲突发生。

// 悲观锁:适用于 写操作频繁、冲突可能性较高 的情况下。它假设经常发生冲突，因此在执行任何可能引起冲突的操作前都会先加锁，以确保数据的一致性和完整性。
// 共享锁锁或读锁)
// 当前事务加 共享锁 后，允许其他事务也对该资源加 共享锁 ，但禁止其他事务对该资源加 排它锁 。
// 在其他事务的共享锁没有释放之前，当前事务和其他事务都禁止对该资源进行修改操作。

// (X 锁或写锁排它锁
// 当前事务加 排它锁 后，禁止其他事务对该资源加 共享锁 或 排它锁排它锁 确保只有一个事务可以对锁定的数据进行读取和修改。在 排它锁 未被释放之前，其他事务不能对该资源加任何形式的锁。
